<?php

declare(strict_types=1);

namespace app\market\controller;

use app\admin\BaseAdminController;
use think\facade\View;
use think\facade\Request;
use think\facade\Config;
use think\facade\Db;
use app\system\model\LogOperate as Log;
use app\system\model\RoleMenu;
use app\system\model\Meta;

class Index extends BaseAdminController
{
    /**
     * 模块(应用)管理页面
     */
    public function index()
    {
        View::assign('title', '模块管理');
        return View::fetch();
    }

    /**
     * 定时任务配置
     */
    public function task()
    {
        $app = $this->getApp();
        if (is_string($app)) {
            return $this->E404();
        }
        $lock = $app['clock'];
        $arr = include $lock;
        if (Request::isAjax()) {
            if ($this->envMode == 'demo'){
                return $this->isDemo();
            }
            $new = [];
            $callback = input('callback');
            foreach ($arr as $item) {
                $call = str_replace('\\', '', $item['callback']);
                if ($call == $callback) {
                    $item['status'] = $item['status'] == 0 ? 1 : 0;
                }
                $new[] = $item;
            }
            file_put_contents($lock, '<?php return ' . var_export($new, true) . ';');
            return json(['msg' => '']);
        }
        View::assign('title', '定时任务/模块管理');
        View::assign('app', $app);
        View::assign('items', $arr);
        return View::fetch();
    }

    /**
     * 保存模块配置信息
     * @param $app 应用名称
     * @param $arr 配置参数列表
     */
    private function updateConfig($app, $arr)
    {
        if ($this->envMode == 'demo'){
            return $this->isDemo();
        }
        $data = [];
        foreach ($arr as $item) {
            $value = input(''.$item['name']);
            if ($item['type'] == 'select2') {
                $value = $value ? implode(',', $value) : '';
            }
            if (isset($item['required']) && $item['required'] && empty($value)) {
                return json(['msg' => $item['title'] . '不能为空']);
            }
            $data[] = [
                'app' => $app['name'],
                'name' => $item['name'],
                'content' => $value,
            ];
        }
        if ($data) {
            Meta::destroy(function($query) use ($app){
                $query->where('app', $app['name']);
            });
            $obj = new Meta;
            $obj->saveAll($data);
        }
        return json(['msg' => '']);
    }

    /**
     * 应用参数配置页面
     */
    public function config()
    {
        $app = $this->getApp();
        if (is_string($app) || !file_exists($app['config'])) {
            return $this->E404();
        }
        $arr = include $app['config'];
        if (Request::isAjax()) {
            return $this->updateConfig($app, $arr);
        }
        $new = [];
        $list = Meta::where('app', $app['name'])->column('content', 'name');
        foreach ($arr as $item) {
            $item['value'] = $list ? ($list[$item['name']] ?? '') : '';
            $new[] = $item;
        }
        View::assign('title', '参数配置/模块管理');
        View::assign('items', $new);
        View::assign('app', $app);
        View::assign('addJs', addJs(['select2', 'sortable']));
        return View::fetch();
    }

    /**
     * 检查是否可配置
     */
    public function check()
    {
        $res = $this->getApp();
        if (is_string($res)) {
            return json(['msg' => $res]);
        }
        return json(['msg' => '']);
    }

    /**
     * 上传应用安装包、解压并安装应用
     */
    public function upload()
    {
        if ($this->envMode == 'demo'){
            return $this->isDemo();
        }
        if (!isset($_FILES['file'])) {
            return json(['msg' => '请选择文件']);
        }
        $file = $_FILES['file'];
        if ($file['error'] != 0) {
            return json(['msg' => '上传失败']);
        }
        $tmp = $file['tmp_name'];
        // 检查是否为zip文件
        $zip = fopen($tmp, 'rb');
        $isZip = $zip && fread($zip, 2) === 'PK' ? true : false;
        fclose($zip);
        if (!$isZip) {
            unlink($tmp);
            return json(['msg' => '请上传ZIP文件']);
        }
        // 解压
        $zip = new \ZipArchive;
        $res = $zip->open($tmp);
        if (!$res) {
            unlink($tmp);
            return json(['msg' => '解压失败']);
        }
        $zip_name = str_replace('.zip', '', $file['name']) . '/';
        $name = $zip->getNameIndex(0);
        if ($name != $zip_name) {
            $zip->close();
            unlink($tmp);
            return json(['msg' => '需对模块目录直接打包']);
        }
        $path = base_path();
        $zip->extractTo($path);
        $zip->close();
        unlink($tmp);
        // 把静态放到 public 下
        $source = $path . $name . $name;
        if (is_dir($source)) {
            $destination = public_path() . 'static/' . $name;
            if (is_dir($destination)) {
                $this->deleteDir($destination);
            }
            rename($source, $destination);
        }
        // 安装启用模块
        $obj = $this->install(str_replace('/', '', $name));
        if ($obj) {
            $json = $obj->getData();
            $msg = $json['msg'];
            if ($msg) {
                return $obj;
            }
        }
        return json(['msg' => '']);
    }

    /**
     * 安装模块
     * @param $name 应用名称
     */
    public function install($name = '')
    {
        if ($this->envMode == 'demo'){
            return $this->isDemo();
        }
        $res = $this->getApp($name);
        if (is_string($res)) {
            return json(['msg' => $res]);
        }
        // 导入
        $err = $this->import($res['sql']);
        if ($err) {
            return json(['msg' => $err]);
        }
        // 标记为已安装
        file_put_contents($res['install'], 'yes');
        // 模块启用
        $delete = $res['delete'];
        if (file_exists($delete)) {
            unlink($delete);
        }
        // 执行PHP文件
        $php = $res['install_php'];
        if (file_exists($php)) {
            include $php;
            $class_name = 'Install'.ucfirst($res['name']);
            $object = new $class_name;
            $object->install();
        }
        if (empty($name)) {
            Log::add('安装模块', [
                '模块名称' => $res['menu']['title'],
            ]);
        }
        return json(['msg' => '']);
    }

    /**
     * 移除模块
     */
    public function uninstall()
    {
        if ($this->envMode == 'demo'){
            return $this->isDemo();
        }
        $res = $this->getApp();
        if (is_string($res)) {
            return json(['msg' => $res]);
        }
        // 1. 删除数据表与数据
        $err = $this->import($res['sql'], true);
        if ($err) {
            return json(['msg' => $err]);
        }
        // 7. 执行PHP文件
        $php = $res['install_php'];
        if (file_exists($php)) {
            include $php;
            $class_name = 'Install'.ucfirst($res['name']);
            $object = new $class_name;
            $object->uninstall();
        }
        // 2. 删除代码目录
        $this->deleteDir($res['code']);
        // 3. 删除静态目录
        $this->deleteDir($res['static']);
        // 4. 删除runtime目录
        $this->deleteDir($res['runtime']);
        // 5. 删除配置数据
        Meta::destroy(function($query) use ($res){
            $query->where('app', $res['name']);
        });
        Log::add('卸载模块', [
            '模块名称' => $res['menu']['title'],
        ]);
        // 6. 删除对应的菜单权限
        RoleMenu::destroy(function($query) use ($res){
            $query->where('role_name', 'like', $res['name'] . '/%');
        });
        return json(['msg' => '']);
    }

    /**
     * 设置模块启用状态
     */
    public function status()
    {
        if ($this->envMode == 'demo'){
            return $this->isDemo();
        }
        $res = $this->getApp();
        if (is_string($res)) {
            return json(['msg' => $res]);
        }
        $lock = $res['delete'];
        if (file_exists($lock)) {
            unlink($lock);
            Log::add('启用模块', [
                '模块名称' => $res['menu']['title'],
            ]);
        } else {
            file_put_contents($lock, 'yes');
            Log::add('停用模块', [
                '模块名称' => $res['menu']['title'],
            ]);
        }
        return json(['msg' => '']);
    }

    /**
     * 读取应用信息列表
     */
    public function data()
    {
        $list = $this->getList();
        return json($list);
    }

    /*************** private ********************/

    /**
     * 读取 mysql 语句
     * @param $file mysql 文件
     * @param $drop 是否删除数据表
     */
    private function getSql($file, $drop)
    {
        $arr = [];
        $prefix = Config::get('database.connections.mysql.prefix');
        $sqlStatements = explode(';', file_get_contents($file));
        foreach ($sqlStatements as $sql) {
            $sql = trim($sql);
            if ($sql) {
                $sql = str_replace('__PREFIX__', $prefix, $sql);
                if ($drop){
                    if (stripos($sql, 'CREATE TABLE') !== false){
                        $pattern = '/CREATE TABLE `([^`]+)`/';
                        if (preg_match($pattern, $sql, $matches)) {
                            $tableName = $matches[1];
                            $arr[] = "DROP TABLE IF EXISTS `$tableName`;";
                        }
                    }
                }else{
                    $arr[] = $sql;
                }
            }
        }
        return $arr;
    }

    /**
     * 导入 mysql 数据
     * @param $file mysql 文件
     * @param $drop 是否删除数据表
     */
    private function import($file, $drop = false)
    {
        if (file_exists($file)) {
            $sqlData = $this->getSql($file, $drop);
            if ($sqlData) {
                Db::startTrans();
                try {
                    foreach ($sqlData as $sql) {
                        Db::execute($sql);
                    }
                    Db::commit();
                } catch (\Exception $e) {
                    // 回滚事务
                    Db::rollback();
                    return $e->getMessage();
                }
            }
        }
        return false;
    }

    /**
     * 读取模块信息
     * @param $name 应用名称
     */
    private function getApp($name = '')
    {
        if (empty($name)) {
            $name = input('name', '');
            if (empty($name)) {
                $name = input('name', '');
            }
        }
        if (in_array($name, ['market', 'system'])) {
            return '系统模块无法操作';
        }
        $list = $this->getList();
        $bool = array_filter($list, function ($list) use ($name) {
            return $list['name'] == $name;
        });
        if (empty($bool)) {
            return '模块不存在';
        }
        $appFolder = base_path() . $name;
        $menuFile = $appFolder . '/config/menu.php'; // 菜单
        $menu = require $menuFile;
        return [
            'name' => $name,
            'sql' => $appFolder . '/config/install.sql', // sql 文件
            'delete' => $appFolder . '/config/delete.lock', // 停用文件
            'install' => $appFolder . '/config/install.lock', // 已安装文件
            'static' => public_path() . 'static/' . $name, // 静态目录
            'runtime' => root_path() . 'runtime/' . $name, // runtime 目录
            'code' => $appFolder, // 模块目录
            'config' => $appFolder . '/config/config.php', // 参数配置
            'clock'  => $appFolder . '/config/clock.php', // 定时任务
            'menu' => $menu,
            'install_php' => $appFolder . '/config/install.php', // 安装或卸载时执行的PHP文件
        ];
    }

    /**
     * 删除目录
     * @param $dir 目录路径
     */
    private function deleteDir($dir)
    {
        if (!is_dir($dir) || is_link($dir)) {
            return false;
        }
        $files = array_diff(scandir($dir), array('.', '..'));
        foreach ($files as $file) {
            (is_dir("$dir/$file")) ? $this->deleteDir("$dir/$file") : unlink("$dir/$file");
        }
        return rmdir($dir);
    }

    /**
     * 读取所有模块信息
     */
    public function getList()
    {
        $dir = base_path();
        $handle = opendir($dir);
        $arr = [];
        while (false !== ($entry = readdir($handle))) {
            if ($entry != "." && $entry != "..") {
                $filePath = $dir . $entry;
                if (is_dir($filePath)) {
                    $menuFile = $filePath . '/config/menu.php'; // 菜单
                    if (file_exists($menuFile)) {
                        $menu = require $menuFile;
                        $arr[] = [
                            'name' => $menu['name'],
                            'icon' => $menu['icon'],
                            'title' => $menu['title'],
                            'priority' => $menu['priority'],
                            'delete' => file_exists($filePath . '/config/delete.lock') ? true : false,
                            'version' => $menu['version'],
                            'installed' => file_exists($filePath . '/config/install.lock') ? true : false,
                            'local' => true,
                            'config' => file_exists($filePath . '/config/config.php') ? true : false,
                            'clock' => file_exists($filePath . '/config/clock.php') ? true : false,
                        ];
                    }
                }
            }
        }
        closedir($handle);
        usort($arr, function ($item1, $item2) {
            return $item1['priority'] <=> $item2['priority'];
        });
        return $arr;
    }
}
